package org.hsc.proxy;
/*
 * ProxyServerManager.java
 *
 * Created on December 22, 2007
 *
 * Name:        Moty Michaely   |   Alon Halfon
 * ID:          043413087       |   300545969
 */

import java.io.*;
import java.net.*;
import java.util.*;

/**
 * The proxy server manager.
 * 
 * @author Alon Halfon
 */
public class ProxyServerManager {
    /*
     * Holds the clients list
     */
    Map<Integer,ReliableUDPClient> clients;
    /*
     * URI represent the input file 
     */
    private URI inputUri;
    /*
     * URI represent the output file 
     */
    private URI outputUri;
    /*
     * time between two manager operations
     */
    private int interval;
    /*
     * in contain the commands file
     */
    private BufferedReader in;
    /*
     * out contain the output file
     */
    private PrintWriter out;
    /*
     * the only c-tor  - gets input and output URI's and time interval
     * and initiate the variables
     */
    ProxyServerManager(URI inputUri,URI outputUri,int interval){
        this.inputUri=inputUri;
        this.outputUri=outputUri;
        this.interval=interval;
        clients=Collections.synchronizedMap(new HashMap<Integer,ReliableUDPClient>());
    }
    
    /**
     * start read commands and commit them.
     */
    public void start() throws IOException, InterruptedException{
       in= new BufferedReader(new FileReader(inputUri.getPath()));
       out= new PrintWriter(new FileWriter (outputUri.getPath()));
       String command=null;
       while ((command=in.readLine())!=null)
       {    
            Thread.sleep(interval);
            //getting the command first word (the "method")
            //we have to decide whether to send the message to specific proxy
            //or to all the proxies.
            String method=command.split(" ",2)[0];
            //make the "KEEP" method become
            //"KEEP "+the second command's word
            if (method.equals("KEEP"))
                method=method+" "+command.split(" ",3)[1];
            if (method.equals("NUM"))//NUM OF PIPE CONNECTIONS
                method=method+" "+command.split(" ",5)[1]+" "+command.split(" ",5)[2]+" "+command.split(" ",5)[3];
            if (method.equals("START"))//START CACHE
                if (command.split(" ",3)[1].equals("CACHE"))//if it's not "CACHE" it's could be just start.
                    method=method+" CACHE";
            if (method.equals("STOP"))//STOP CACHE
                if (command.split(" ",3)[1].equals("CACHE"))//if it's not "CACHE" it's could be just start.
                    method=method+" CACHE";
            if (method.equals("CACHE"))//CACHE SIZE
                method=method+" "+command.split(" ",3)[1];
            if (method.equals("CLEAR"))//CLEAR CACHE
                method=method+" "+command.split(" ",3)[1];
            
            //now method contain the complete command (with the spaces)
            
            if (method.equals("START") 
                    || method.equals("CLOSE") 
                    || method.equals("EXIT")
                    || method.equals("NUM OF PIPE CONNECTIONS")
                    || method.equals("START CACHE")
                    || method.equals("STOP CACHE")
                    || method.equals("CACHE SIZE")
                    || method.equals("CLEAR CACHE")
                    || method.equals("MAX"))
            {
                ManagerCommand manCommand=null;
                if (method.equals("START"))
                {
                    //second word is the udp port
                    int udpPort=Integer.parseInt(command.split(" ",4)[1]);
                    //third word is the server's ip
                    String serverIP=command.split(" ",4)[2];
                    //4th word is the http port
                    int httpPort=Integer.parseInt(command.split(" ",4)[3]);
                    
                    InetAddress inetAddress=InetAddress.getByName(serverIP);
                    InetSocketAddress address=new InetSocketAddress(inetAddress, httpPort);
                    ReliableUDPClient reliUDP=new ReliableUDPClient(udpPort, address, interval);
                    
                    
                    manCommand=new ManagerCommand(reliUDP, "START "+String.valueOf(httpPort));
                    //send the command to the client in new thread
                    manCommand.start();
                    manCommand.join();//waiting the command's reply (or timed out)
                    //adding the proxy server only if it started
                    if (manCommand.response.equals("START OK"))
                       this.addClient(reliUDP); 
                    
                }if (method.equals("NUM OF PIPE CONNECTIONS"))
                {
                    //sixth word is the udp port
                    Integer udpPort = new Integer(command.split(" ",6)[5]);
                    ReliableUDPClient currClient=(ReliableUDPClient)clients.get(udpPort);
                    if (currClient==null)
                        out.println("SERVER DOWN");
                    else//server is online
                    {
                        manCommand=new ManagerCommand(currClient, method+" "+udpPort.toString());
                        manCommand.start();
                        //waiting the command's reply (or timed out)
                        manCommand.join();
                    }

                }if (method.equals("START CACHE"))
                {
                    //third word is the udp port
                    Integer udpPort = new Integer(command.split(" ",3)[2]);
                    ReliableUDPClient currClient=(ReliableUDPClient)clients.get(udpPort);
                    if (currClient==null)
                        out.println("SERVER DOWN");
                    else//server is online
                    {
                        manCommand=new ManagerCommand(currClient, method);
                        manCommand.start();
                        //waiting the command's reply (or timed out)
                        manCommand.join();
                    }
                }else if (method.equals("STOP CACHE"))
                {
                    //third word is the udp port
                    Integer udpPort = new Integer(command.split(" ",3)[2]);
                    ReliableUDPClient currClient=(ReliableUDPClient)clients.get(udpPort);
                    if (currClient==null)
                        out.println("SERVER DOWN");
                    else//server is online
                    {
                        manCommand=new ManagerCommand(currClient, method);
                        manCommand.start();
                        //waiting the command's reply (or timed out)
                        manCommand.join();
                    }
                }else if (method.equals("CACHE SIZE"))
                {
                    //forth word is the udp port
                    Integer udpPort = new Integer(command.split(" ",4)[3]);
                    ReliableUDPClient currClient=(ReliableUDPClient)clients.get(udpPort);
                    if (currClient==null)
                        out.println("SERVER DOWN");
                    
                    else//server is online
                    {
                        String cacheSize=command.split(" ",4)[3];
                        manCommand=new ManagerCommand(currClient, method+" "+cacheSize);
                        manCommand.start();
                        //waiting the command's reply (or timed out)
                        manCommand.join();
                    }
                }else if (method.equals("CLEAR CACHE"))
                {
                    //third word is the udp port
                    Integer udpPort = new Integer(command.split(" ",3)[2]);
                    ReliableUDPClient currClient=(ReliableUDPClient)clients.get(udpPort);
                    if (currClient==null)
                        out.println("SERVER DOWN");
                    else//server is online
                    {
                        manCommand=new ManagerCommand(currClient, method);
                        manCommand.start();
                        //waiting the command's reply (or timed out)
                        manCommand.join();
                    }
                }else if (method.equals("MAX"))
                {
                    //third word is the udp port
                    Integer udpPort = new Integer(command.split(" ",3)[2]);
                    ReliableUDPClient currClient=(ReliableUDPClient)clients.get(udpPort);
                    if (currClient==null)
                        out.println("SERVER DOWN");
                    else//server is online
                    {
                        String maxSize=command.split(" ",3)[1];
                        manCommand=new ManagerCommand(currClient, method+" "+maxSize);
                        manCommand.start();
                        //waiting the command's reply (or timed out)
                        manCommand.join();
                    }
                }else if (method.equals("CLOSE")||method.equals("EXIT") )//CLOSE or EXIT
                    
                {
                    //second word is the udp port
                    Integer udpPort = new Integer(command.split(" ",4)[1]);
                    ReliableUDPClient currClient=(ReliableUDPClient)clients.get(udpPort);
                    
                    //hashmap return null if value is not exist for the specific key
                    if (currClient==null)
                        out.println("SERVER DOWN");
                    else
                    {
                        manCommand=new ManagerCommand(currClient, method);
                        manCommand.start();
                        //waiting the command's reply (or timed out)
                        manCommand.join();
                        //if the response is EXIT OK the server is taking out the server's list
                        if (manCommand.response.equals("EXIT OK"))
                            clients.remove(udpPort);
                    }
                }
            }
            else if (method.equals("KEEP ALIVE") || method.equalsIgnoreCase("CLIENTS"))
            {
                ReliableUDPClient currClient=null;
                LinkedList<ManagerCommand> commandsList=new LinkedList<ManagerCommand>();
                ManagerCommand manCommand=null;
                Iterator<Integer> keys = clients.keySet().iterator();
                while( keys.hasNext() )
                {          
                    currClient=clients.get(keys.next());
                    manCommand=new ManagerCommand(currClient, method);
                    manCommand.start();
                    commandsList.add(manCommand);
                }
                //waiting for the all commands to finish
                while (!commandsList.isEmpty()){
                    manCommand=(ManagerCommand)commandsList.removeFirst();
                    manCommand.join();

                }
                
            }
            else//not any of the commands
                out.println("Unknown Command: "+method);
       }
       in.close();
       out.close();
       Iterator<Integer> keys = clients.keySet().iterator();
       ReliableUDPClient currClient;
        while( keys.hasNext() )
        {          
            currClient=clients.get(keys.next());
            currClient.closeSocket();
        }
     }
     //TODO add Reliable close?
    
    /**
     * add new ReliableUDPClient to the clients map
     */  
     public void addClient(ReliableUDPClient reliUDP){
         clients.put(new Integer(reliUDP.getPort()),reliUDP);
     }
    /**
     * class get ReliableUDPClient and command, and send it to the client in new thread (by run function)
     */
     public class ManagerCommand extends Thread{
            /*
             * the command we should execute
             */
            String command;
            /*
             * 
             */
            String response=null;
            /*
             * the connection we will use to send the message
             */
            
            ReliableUDPClient client;
            
            /*
             * ManagerCommand C-tor
             */
            ManagerCommand(ReliableUDPClient client,String command){
                this.command=command;
                this.client=client;
            }
            /*
             * run function will be called by start function (ManagerCommand implements thread)
             */
            public void run(){
                try
                {
                    response=client.send(command);
                    out.println(response);
                }
                catch (TimedOutException timedOut)
                {
                    out.println(timedOut.getMessage());
                    response="Server is down this response should not be printed";
                }
                catch (IOException e)
                {
                    System.err.println(e.getMessage());
                }
                
            }
        }

}
